#include "apl_zigbee_proto.h"
#include "stm32l5xx_hal.h"
#include "md_5.h"
#include "fml_zigbee.h"
#include "fml_ota.h"
#include "hal_zigbee_uart.h"


#define DEV_ADDR            2
#define REQ_PKT_HEAD1       0xAA
#define REQ_PKT_HEAD2       0x55
#define REQ_PKT_EOF         0xBB

#define RESP_PKT_HEAD1      0xAA
#define RESP_PKT_HEAD2      0x56
#define RESP_PKT_EOF        0xBB

#define PERIOD_TIME         5000

#define MD5_SIZE            32

typedef enum
{
    PROTO_STAT_IDLE = -1,
    PROTO_STAT_PERIOD_REQ = 0,
    PROTO_STAT_FILE_DL_REQ,
    PROTO_STAT_FILE_DL_FINISH,
}PROTO_STAT;

typedef enum
{
    WAIT_PKT_HEAD1 = 0,
    WAIT_PKT_HEAD2,
    WAIT_PKT_LEN_L,
    WAIT_PKT_LEN_H,
    WAIT_PKT_DEV_ADDR_L,
    WAIT_PKT_DEV_ADDR_H,
    WAIT_PKT_TYPE,
    WAIT_PKT_DATA,
    WAIT_PKT_CRC,
    WAIT_PKT_EOF,
}PROTO_PARSE_STAT;


typedef enum
{
    REQ_TYPE_FILE_UPDATE_QUERY = 1,
    REQ_TYPE_FILE_DOWNLOAD,
}REQ_TYPE;

typedef enum
{
    FILE_TYPE_LCD_INFO = 1,
    FILE_TYPE_OTA,
}FILE_TYPE;

#pragma pack(1)
typedef struct
{
    uint8_t pkt_head1;
    uint8_t pkt_head2;
    uint16_t pkt_len;
    uint16_t dev_addr;
    uint8_t req_type;
    uint8_t crc;
    uint8_t pkt_eof;
}REQUEST_PKT;

typedef struct
{
    FILE_TYPE type;
    uint32_t pkt_index;
    uint32_t p_index;
    bool wait_ack;
    uint32_t file_size;
    uint32_t pkt_total;
    uint8_t md5_local[16];
    uint8_t md5_target[16];
}FILE_DL_STR;

#pragma pack(1)
typedef struct
{
    uint8_t pkt_head1;
    uint8_t pkt_head2;
    uint16_t pkt_len;
    uint16_t dev_addr;
    uint8_t req_type;
    uint8_t data[100];
    uint8_t crc;
    uint8_t pkt_eof;
}RESP_PKT;


uint32_t g_timestamp = 0;
PROTO_STAT g_proto_stat = PROTO_STAT_PERIOD_REQ;
uint8_t g_lcd_info_data[6*1024] = {0};
FILE_DL_STR g_file_dl_str;
extern void parse_objects(const char* str);

static void print_hex(void *addr, uint32_t len)
{
    int j = 0;
    
    for (j = 0; j < len; j++)
    {
        if ((j % 32 == 0) && j)
        {
            printf("\r\n");
        }
        printf("%02X ", ((uint8_t *)addr)[j]);
    }
    printf("\r\n");
}

void dump_hex(void *addr, uint32_t len)
{
    int j = 0;
    
    for (j = 0; j < len; j++)
    {
        if ((j % 32 == 0) && j)
        {
            printf("\r\n");
        }
        printf("%02X ", ((uint8_t *)addr)[j]);
    }
}


void hex_to_ascii(char *p_out, uint8_t *p_in, int len)
{
    int i = 0;
    int j = 0;
    uint8_t hex = 0;

    for (; i < len; i++)
    {
        hex = p_in[i] >> 4;
        if (hex >= 0 && hex <= 9)
        {
            p_out[j++] = hex + '0';
        }
        else
        { //(hex >= 0xa && hex <= 0xf)
            p_out[j++] = hex - 0xa + 'a';
        }

        hex = p_in[i] & 0xF;
        if (hex >= 0 && hex <= 9)
        {
            p_out[j++] = hex + '0';
        }
        else
        { //(hex >= 0xa && hex <= 0xf)
            p_out[j++] = hex - 0xa + 'a';
        }
    }
}

bool calc_md5_for_string(uint8_t *p_in, int in_len, uint8_t *p_md5, uint8_t md5_len)
{
    uint8_t digest[16] = {0};
    MD5_CTX_T ctx;
    //uint8_t i = 0;

    if ((NULL == p_in) || (0 >= in_len) || (NULL == p_md5) || (md5_len < MD5_SIZE)) {
        return false;
    }

    md5_init(&ctx);
    md5_update(&ctx, p_in, in_len);
    md5_final(&ctx, digest);

    hex_to_ascii((char *)p_md5, digest, sizeof(digest));
#if 0
    for (i = 0; i < 32; i++) {
        blog_d("%c", p_md5[i]);
    }
    blog_d("\n");
#endif
    return true;
}


//只取累加和最低一个字节
unsigned char zma_check(unsigned char *chBuf, int iSize)
{
    int icnt = 0;
    unsigned char chCheck = 0;
    
    for ( icnt = 0; icnt < iSize; icnt++)
    {
        chCheck += chBuf[icnt];
    }

    chCheck = ~chCheck;
    chCheck = chCheck + 1;

    return chCheck;
}

int32_t make_requset_data(uint8_t *buff, uint16_t buff_len, REQ_TYPE req_type, uint8_t *data, uint16_t data_len)
{
    REQUEST_PKT req_pkt;

    if (buff == NULL)
        return -1;

    req_pkt.pkt_head1 = REQ_PKT_HEAD1;
    req_pkt.pkt_head2 = REQ_PKT_HEAD2;
    req_pkt.pkt_len = sizeof(req_pkt) + data_len;
    req_pkt.dev_addr = DEV_ADDR;
    req_pkt.req_type = req_type;

    if (buff_len < req_pkt.pkt_len)
        return -2;

    memcpy(buff, (uint8_t *)&req_pkt, 7);
    if (data)
        memcpy(buff + 7, data, data_len);

    req_pkt.crc = zma_check(buff + 4, 3 + data_len);
    
    buff[7+data_len] = req_pkt.crc;
    buff[7+data_len+1] = REQ_PKT_EOF;

    return req_pkt.pkt_len;
}

int32_t send_proto_requset(uint8_t *buff, uint16_t buff_len, REQ_TYPE req_type, uint8_t *data, uint16_t data_len)
{
    int32_t send_len = 0;
    
    if ((buff == NULL) || (buff_len == 0))
        return -1;
    
    send_len = make_requset_data(buff, buff_len, req_type, data, data_len);
    if (send_len <= 0)
        return -2;

    printf("C--->G: %d byte\n", send_len);
    print_hex(buff, send_len);
    fml_zigbee_send(buff, send_len);

    return send_len;
}

int32_t send_file_update_query_req(void)
{
    uint8_t buff[100] = {0};
    uint8_t version[4] = {1, 1, 1, 1};

    return send_proto_requset(buff, sizeof(buff), REQ_TYPE_FILE_UPDATE_QUERY, version, sizeof(version));
}

int32_t send_file_download_req(FILE_TYPE file_type, uint32_t file_no)
{
    uint8_t buff[100] = {0};
    uint8_t data[5] = {0};

    data[0] = file_type;
    *((uint32_t *)&data[1]) = file_no;

    return send_proto_requset(buff, sizeof(buff), REQ_TYPE_FILE_DOWNLOAD, data, sizeof(data));
}

void app_init(void)
{
    g_timestamp = HAL_GetTick();
    memset(&g_zigbee_uart, 0, sizeof(g_zigbee_uart));
    memset(&g_file_dl_str, 0, sizeof(g_file_dl_str));
}

void apl_business_task(void)
{
    uint8_t write_buff[8] = {0};
    uint8_t reboot_flag = 0;
    
    switch (g_proto_stat)
    {
        case PROTO_STAT_IDLE:
        {
            break;
        }
        case PROTO_STAT_PERIOD_REQ:
        {
            uint32_t timestamp = HAL_GetTick();
            if(timestamp - g_timestamp > PERIOD_TIME)
            {
                g_timestamp = timestamp;
                send_file_update_query_req();
            }
            break;
        }
        case PROTO_STAT_FILE_DL_REQ:
        {
            if ((!g_file_dl_str.wait_ack) || ((HAL_GetTick() - g_timestamp) > 2000))
            {
                g_timestamp = HAL_GetTick();
                send_file_download_req(g_file_dl_str.type, g_file_dl_str.pkt_index);
                g_file_dl_str.wait_ack = true;
            }
            break;
        }
        case PROTO_STAT_FILE_DL_FINISH:
        {
            if (g_file_dl_str.type == FILE_TYPE_LCD_INFO)
            {
                calc_md5_for_string(g_lcd_info_data, g_file_dl_str.file_size, g_file_dl_str.md5_local, sizeof(g_file_dl_str.md5_local));
                printf("================================================================================\n");
                printf("%s\n", g_lcd_info_data);
                printf("================================================================================\n");
                parse_objects(g_lcd_info_data);
                
                memset(g_lcd_info_data, 0, sizeof(g_lcd_info_data));
                
                send_file_download_req(g_file_dl_str.type, 0xFFFFFFFF);
                memset(&g_file_dl_str, 0, sizeof(g_file_dl_str));
                g_proto_stat = PROTO_STAT_PERIOD_REQ;
            }
            else if (g_file_dl_str.type == FILE_TYPE_OTA)
            {
                if (0 == fml_ota_app_md5_calc(APP_BAK_START_ADDR, g_file_dl_str.file_size, g_file_dl_str.md5_local))
                {
                    if (0 == memcmp(g_file_dl_str.md5_local, g_file_dl_str.md5_target, 16))
                    {
                        printf("OTA File Download Success!!!\n");
                        
                        send_file_download_req(g_file_dl_str.type, 0xFFFFFFFF); 
                        

                        if (0 == fml_ota_flash_erase(CONFIG_START_ADDR, CONFIG_SIZE))
                            printf("Erase Config Flash\n"); 
                        
                        *(uint32_t *)(&write_buff[0]) = 0xAA5555AA;
                        *(uint32_t *)(&write_buff[4]) = g_file_dl_str.file_size;
                        
                        if (0 == fml_ota_flash_write(write_buff, CONFIG_START_ADDR, sizeof(write_buff)))
                        {
                            printf("Write Config Success!!!\n");
                            reboot_flag = 1;
                        } 
                        
                        memset(&g_file_dl_str, 0, sizeof(g_file_dl_str));
                        //g_proto_stat = PROTO_STAT_PERIOD_REQ;
                        
                        if (reboot_flag)
                        {
                            printf("System Reboot!!!\n");
                            HAL_Delay(2000);
                            //__disable_irq();
                            __set_FAULTMASK(1);
                            HAL_NVIC_SystemReset();
                            while (1);
                        }
                    }
                }
            }
            break;
        }
        default:
        {
            g_proto_stat = PROTO_STAT_PERIOD_REQ;
        }
    }
}


PROTO_PARSE_STAT g_proto_parse_stat = WAIT_PKT_HEAD1;
RESP_PKT g_resp_pkt;

int32_t app_process_pkt(RESP_PKT *resp_pkt)
{
    if (resp_pkt->req_type == REQ_TYPE_FILE_UPDATE_QUERY)
    {
        if (resp_pkt->data[0] == FILE_TYPE_LCD_INFO)
        {
            g_file_dl_str.type = FILE_TYPE_LCD_INFO;
            g_file_dl_str.pkt_index = 1;
            g_file_dl_str.p_index = 0;
            g_file_dl_str.file_size = *(uint32_t *)&resp_pkt->data[1];
            g_file_dl_str.pkt_total = *(uint32_t *)&resp_pkt->data[5];
            memcpy(g_file_dl_str.md5_target, &resp_pkt->data[9], 16);
            printf("file size:%d\n", g_file_dl_str.file_size);
            printf("packet num:%d\n", g_file_dl_str.pkt_total);
            printf("file md5:");
            print_hex(g_file_dl_str.md5_target, 16);
            
            g_proto_stat = PROTO_STAT_FILE_DL_REQ;
        }
        else if (resp_pkt->data[0] == FILE_TYPE_OTA)
        {
            g_file_dl_str.type = FILE_TYPE_OTA;
            g_file_dl_str.pkt_index = 1;
            g_file_dl_str.p_index = 0;
            g_file_dl_str.file_size = *(uint32_t *)&resp_pkt->data[1];
            g_file_dl_str.pkt_total = *(uint32_t *)&resp_pkt->data[5];
            memcpy(g_file_dl_str.md5_target, &resp_pkt->data[9], 16);
            printf("file size:%d\n", g_file_dl_str.file_size);
            printf("packet num:%d\n", g_file_dl_str.pkt_total);
            printf("file md5:");
            print_hex(g_file_dl_str.md5_target, 16);
            
            if (0 == fml_ota_flash_erase(APP_BAK_START_ADDR, g_file_dl_str.file_size))
            {
                printf("Erase Flash 0x%08x size %d\n", APP_BAK_START_ADDR, g_file_dl_str.file_size);
            }
            g_proto_stat = PROTO_STAT_FILE_DL_REQ;
        }
    }
    else if (resp_pkt->req_type == REQ_TYPE_FILE_DOWNLOAD)
    {
        uint32_t cp_len = resp_pkt->pkt_len - 9 - 5;
        if ((resp_pkt->data[0] == FILE_TYPE_LCD_INFO) && (*(uint32_t *)&resp_pkt->data[1] == g_file_dl_str.pkt_index))
        {
            printf("Json File total: %d cur: %d copy len: %d\n", g_file_dl_str.pkt_total, g_file_dl_str.pkt_index, cp_len);
            memcpy(g_lcd_info_data+g_file_dl_str.p_index, &resp_pkt->data[5], cp_len);
            g_file_dl_str.p_index += cp_len;
            g_file_dl_str.pkt_index++;
            g_file_dl_str.wait_ack = false;
            if (g_file_dl_str.pkt_index > g_file_dl_str.pkt_total)
            {
                g_proto_stat = PROTO_STAT_FILE_DL_FINISH;
            }
            
        }
        else if ((resp_pkt->data[0] == FILE_TYPE_OTA) && (*(uint32_t *)&resp_pkt->data[1] == g_file_dl_str.pkt_index))
        {
            printf("OTA File total: %d cur: %d copy len: %d\n", g_file_dl_str.pkt_total, g_file_dl_str.pkt_index, cp_len);
            //memcpy(g_lcd_info_data+g_file_dl_str.p_index, &resp_pkt->data[5], cp_len);
            fml_ota_flash_write(&resp_pkt->data[5], APP_BAK_START_ADDR + g_file_dl_str.p_index, cp_len);
            g_file_dl_str.p_index += cp_len;
            g_file_dl_str.pkt_index++;
            g_file_dl_str.wait_ack = false;
            if (g_file_dl_str.pkt_index > g_file_dl_str.pkt_total)
            {
                g_proto_stat = PROTO_STAT_FILE_DL_FINISH;
            }  
        }
        
    }
}

#if 0
void app_proto_parse_reset(PROTO_PARSE_STAT *parse_stat)
{
    memset(parse_stat, 0, sizeof(PROTO_PARSE_STAT));
}
#else
void app_proto_parse_reset(void)
{
    g_proto_parse_stat = WAIT_PKT_HEAD1;
    memset(&g_resp_pkt, 0, sizeof(g_resp_pkt));
    
}
#endif

void app_proto_parse(uint8_t data)
{
    static uint16_t pkt_data_len = 0;
    static uint8_t *p_data = NULL;

    switch (g_proto_parse_stat)
    {
        case WAIT_PKT_HEAD1:
        {
            if (data == RESP_PKT_HEAD1)
            {
                g_resp_pkt.pkt_head1 = data;
                g_proto_parse_stat = WAIT_PKT_HEAD2;
            }
            else
            {
                goto reset;
            }
            break;
        }
        case WAIT_PKT_HEAD2:
        {
            if (data == RESP_PKT_HEAD2)
            {
                g_resp_pkt.pkt_head2 = data;
                g_proto_parse_stat = WAIT_PKT_LEN_L;
            }
            else
            {
                goto reset;
            }
            break;
        }
        case WAIT_PKT_LEN_L:
        {
            g_resp_pkt.pkt_len = data;
            g_proto_parse_stat = WAIT_PKT_LEN_H;
            break;
        }
        case WAIT_PKT_LEN_H:
        {
            g_resp_pkt.pkt_len |= ((uint16_t)data << 8);
            g_proto_parse_stat = WAIT_PKT_DEV_ADDR_L;
            break;
        }
        case WAIT_PKT_DEV_ADDR_L:
        {
            g_resp_pkt.dev_addr = data;
            g_proto_parse_stat = WAIT_PKT_DEV_ADDR_H;
            break;
        }
        case WAIT_PKT_DEV_ADDR_H:
        {
            g_resp_pkt.dev_addr |= ((uint16_t)data << 8);
            g_proto_parse_stat = WAIT_PKT_TYPE;
            break;
        }
        case WAIT_PKT_TYPE:
        {
            g_resp_pkt.req_type = data;
            g_proto_parse_stat = WAIT_PKT_DATA;
            pkt_data_len = g_resp_pkt.pkt_len - 9;
            p_data = g_resp_pkt.data;
            break;
        }
        case WAIT_PKT_DATA:
        {
            *p_data++ = data;
            if (--pkt_data_len == 0)
            {
                g_proto_parse_stat = WAIT_PKT_CRC;
            }
            break;
        }
        case WAIT_PKT_CRC:
        {
            g_resp_pkt.crc = data;
            g_proto_parse_stat = WAIT_PKT_EOF;
            break;
        }
        case WAIT_PKT_EOF:
        {
            if (data == RESP_PKT_EOF)
            {
                g_resp_pkt.pkt_eof = data;
                if (g_resp_pkt.crc == zma_check((uint8_t *)&g_resp_pkt + 4, g_resp_pkt.pkt_len - 6))
                {
                    printf("G--->C: %d byte\r\n", g_resp_pkt.pkt_len);
                    dump_hex(&g_resp_pkt, g_resp_pkt.pkt_len - 2);
                    print_hex(&g_resp_pkt.crc, 2);
                    app_process_pkt(&g_resp_pkt);
                    app_proto_parse_reset();
                }
                else
                {
                    goto reset;
                }
            }
            else
            {
                goto reset;
            }
            break;
        }
        default:
        {
            break;
        }
    }

    return;
    
reset:

    pkt_data_len = 0;
    p_data = NULL;
    app_proto_parse_reset();

    return;
}

uint8_t test_buff[] = {0xAA, 0x56, 0x0A, 0x00, 0x01, 0x00, 0x01, 0x00, 0xFE, 0xBB};

void apl_proto_parse_task(void)
{
    uint32_t i;
    uint8_t data;

    if (fml_zigbee_recv(&data, 1) > 0)
    {
        //printf("%02x ", data[i]);
        app_proto_parse(data);
    }
}


